<?php

namespace Fluency\Web\Routing;

use Fluency\Web\Http\Request;

/**
 * A router that matches the given URL against a list of patterns corresponding to specified action
 * names and (optionally) parameters. It can also capture request parameter values from the URL.
 *
 * @package Fluency\Web\Routing
 * @author  Ignas Rudaitis
 * @since   1.0
 */
class PatternRouter implements Router
{
    const PARAM_REGEX = '/\<(?<name>[a-zA-Z][a-zA-Z0-9\-_]+)(:(?<format>.+?))?\>/';
    const OPTIONAL_SEGMENT_REGEX = '/\)(?!\?)/';
    const OPTIONAL_SEGMENT_REPLACEMENT = ')?';

    private $routes = array();

    /**
     * The constructor. It takes an array of requests indexed by URL patterns. The patterns are
     * matched with the URL from the beginning to the end, because they are converted to regexes
     * wrapped in ^ and $. However, any trailing or leading slashes are trimmed from the URL
     * beforehand.
     *
     * To capture a request parameter from the URL, angle brackets are used, for example,
     * posts/<id>. Format constraints can be specified for the parameters after a colon using PCRE
     * syntax, for example, posts/page/<page:\d+>. Optional segments of URLs can be specified using
     * parentheses, for example, posts(/<page>). If the request that the pattern is mapped to
     * already contains parameters, they are overridden with the captured ones. Therefore,
     * parameters specified in the "routes" can be used as default values for optional parameters.
     *
     * @param Fluency\Web\Http\Request[] $routes a mapping of patterns to requests
     */
    public function __construct(array $routes)
    {
        foreach ($routes as $pattern => $route) {
            $this->routes[$this->convertPatternToRegex($pattern)] = $route;
       }
    }

    /**
     * @see Fluency\Web\Routing\Router::query()
     *
     * @param string $url
     * @return Fluency\Web\Http\Request|null
     */
    public function query($url)
    {
        $url = str_replace("//", "/", trim($url, "/"));
        foreach ($this->routes as $pattern => $route) {
            if (preg_match($pattern, $url, $matches)) {
                foreach ($matches as $name => $value) {
                    if (is_string($name)) {
                        $route = $route->withParam($name, $value);
                    }
                }
                return $route;
            }
        }
    }

    private function convertPatternToRegex($pattern)
    {
        $pattern = preg_replace(self::OPTIONAL_SEGMENT_REGEX, self::OPTIONAL_SEGMENT_REPLACEMENT,
                $pattern);
        preg_match_all(self::PARAM_REGEX, $pattern, $matches, PREG_SET_ORDER);
        foreach ($matches as $match) {
            $name = $match["name"];
            $format = isset($match["format"]) ? $match["format"] : ".+?";
            $pattern = str_replace($match[0], "(?<$name>$format)", $pattern);
        }
        $pattern = "#^" . $pattern . "$#";
        return $pattern;
    }
}
